package com.gitee.wsl.doc.prosemirror.transform.step

import com.gitee.wsl.doc.prosemirror.exception.RangeError
import com.gitee.wsl.doc.prosemirror.model.Schema
import com.gitee.wsl.doc.prosemirror.model.fragment.Fragment
import com.gitee.wsl.doc.prosemirror.model.fragment.Slice
import com.gitee.wsl.doc.prosemirror.model.mark.Mark
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.transform.map.Mappable
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlin.Int

// Remove a mark from a specific node.
class RemoveNodeMarkStep(
    // The position of the target node.
    val pos: Int,
    // The mark to remove.
    val mark: Mark
) : Step() {
    override val from: Int
        get() = 0
    override val to: Int
        get() = 0

    override fun apply(doc: Node): StepResult {
        val node = doc.nodeAt(pos) ?: return StepResult.Companion.fail("No node at mark step's position")
        val updated = node.type.create(node.attrs, null as Fragment?, mark.removeFromSet(node.marks))
        return StepResult.Companion.fromReplace(
            doc,
            pos,
            pos + 1,
            Slice(Fragment.Companion.from(updated), 0, if (node.isLeaf) 0 else 1)
        )
    }

    override fun invert(doc: Node): Step {
        val node = doc.nodeAt(pos)
        if (node == null || !mark.isInSet(node.marks)) return this
        return AddNodeMarkStep(pos, mark)
    }

    override fun map(mapping: Mappable): Step? {
        val pos = mapping.mapResult(this.pos, 1)
        return if (pos.deletedAfter) null else RemoveNodeMarkStep(pos.pos, this.mark)
    }

    override fun toJSON() = buildJsonObject {
        put("stepType", "removeNodeMark")
        put("pos", pos)
        put("mark", mark.toJSON())
    }

    companion object : StepJsonParser<RemoveNodeMarkStep> {
        init {
            jsonID("removeNodeMark", this)
        }

        override fun fromJSON(schema: Schema, json: JsonObject): RemoveNodeMarkStep {
            val pos = json.get("pos")?.jsonPrimitive?.intOrNull
                ?: throw RangeError("Invalid input for RemoveNodeMarkStep.fromJSON")
            return RemoveNodeMarkStep(pos, schema.markFromJSON(json["mark"]?.jsonObject))
        }
    }
}

fun Step.Companion.removeNodeMark(
    // The position of the target node.
     pos: Int,
    // The mark to remove.
     mark: Mark
) = RemoveNodeMarkStep(pos, mark)